﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using CoreLibrary;
using CoreLibrary.Repo;
using Entities.DataBase.Obj;
using Entities.DataBase.Persistent;

namespace Entities.DataBase.Impl {

    public class PurchaseRecordRepository : BaseRepository<PurchaseRecord>, IPurchaseRecordRepository {

        private static readonly Func<DataContext, int, string, Stock>
          LinqGetStockByLocationProductId = CompiledQuery.Compile((DataContext context, int location, string productId)
               => context.GetTable<Stock>().SingleOrDefault(o => o.Location == location && o.ProductId.Equals(productId)));

        private static readonly Func<DataContext, string, PurchaseRecord>
            LinqGetRecord = CompiledQuery.Compile((DataContext context, string recordId)
                => context.GetTable<PurchaseRecord>().SingleOrDefault(o => o.Id.Equals (recordId)));
        
        private static readonly Func<DataContext, string, int, IQueryable<PurchaseRecord>>
            LinqGetRecordsByProductIdLocation = CompiledQuery.Compile((DataContext context, string productId, int location)
                => context.GetTable<PurchaseRecord>()
                          .Where(o => o.ProductId.Equals(productId) && o.Location == location));

        private static readonly Func<DataContext, string, IQueryable<PurchaseRecordInfo>>
            LinqGetRecordsByKeyword = CompiledQuery.Compile((DataContext context, string keyword)
              => (
                  from product in context.GetTable<Product>()
                                         .Where(p => "".Equals(keyword)
                                             || p.Name.Contains(keyword)
                                             || p.CName.Contains(keyword)
                                             || p.Description.Contains(keyword)
                                             || p.Keywords.Contains(keyword))
                  join record in context.GetTable<PurchaseRecord>().OrderByDescending(p => p.DateCreated)
                  on product.ProductId equals record.ProductId
                  select new PurchaseRecordInfo {
                      Id             = record.Id,
                      ProductId      = product.ProductId,
                      OperatorId     = record.OperatorId,
                      PostOperatorId = record.PostOperatorId,
                      ProductName    = product.CName,
                      Name           = product.Name,
                      Cost           = record.Cost,
                      CostCurrency   = record.CostCurrency,
                      Count          = record.Count,
                      Postage        = record.Postage,
                      Location       = record.Location,
                      Rate           = (double) record.CurrencyRate,
                      DateCreated    = record.DateCreated,
                      DateUpdated    = record.DateUpdated
                  }
                 )
        );

        private static readonly Func<DataContext, int, IQueryable<PurchaseRecordInfo>>
            LinqGetRecordsByLocation   = CompiledQuery.Compile((DataContext context, int location)
              => (
                  from product in context.GetTable<Product>()
                  join record  in context.GetTable<PurchaseRecord>()
                                         .Where(s => s.Location == location)
                                         .OrderByDescending(p => p.DateCreated)
                  on product.ProductId equals record.ProductId
                  select new PurchaseRecordInfo {
                      Id             = record.Id,
                      ProductId      = product.ProductId,
                      OperatorId     = record.OperatorId,
                      PostOperatorId = record.PostOperatorId,
                      ProductName    = product.CName,
                      Name           = product.Name,
                      Cost           = record.Cost,
                      CostCurrency   = record.CostCurrency,
                      Count          = record.Count,
                      Postage        = record.Postage,
                      Location       = record.Location,
                      Rate           = (double)record.CurrencyRate,
                      DateCreated    = record.DateCreated,
                      DateUpdated    = record.DateUpdated
                  }
                 )
        );

        private static readonly Func<DataContext, string, IQueryable<PurchaseRecordInfo>>
          LinqGetRecordsByProductId = CompiledQuery.Compile((DataContext context, string productID)
              => (
                  from product in context.GetTable<Product>().Where(p => p.ProductId.Equals(productID))
                  join record  in context.GetTable<PurchaseRecord>().OrderByDescending(p => p.DateCreated)
                  on product.ProductId equals record.ProductId
                  select new PurchaseRecordInfo {
                      Id             = record.Id,
                      OperatorId     = record.OperatorId,
                      PostOperatorId = record.PostOperatorId,
                      ProductId      = record.ProductId,
                      ProductName    = product.CName,
                      Name           = product.Name,
                      Cost           = record.Cost,
                      CostCurrency   = record.CostCurrency,
                      Count          = record.Count,
                      Postage        = record.Postage,
                      Location       = record.Location,
                      Rate           = (double)record.CurrencyRate,
                      DateCreated    = record.DateCreated,
                      DateUpdated    = record.DateUpdated
                  }
                 )
        );

        private static readonly Func<DataContext, IQueryable<PurchaseRecordInfo>>
          LinqGetAllRecords = CompiledQuery.Compile((DataContext context)
              => (
                  from product in context.GetTable<Product>().OrderByDescending(p => p.DateCreated)
                  join record  in context.GetTable<PurchaseRecord>().OrderByDescending(p => p.DateCreated)
                  on product.ProductId equals record.ProductId
                  select new PurchaseRecordInfo {
                      Id             = record.Id,
                      OperatorId     = record.OperatorId,
                      PostOperatorId = record.PostOperatorId,
                      ProductId      = record.ProductId,
                      ProductName    = product.CName,
                      Name           = product.Name,
                      Cost           = record.Cost,
                      CostCurrency   = record.CostCurrency,
                      Count          = record.Count,
                      Postage        = record.Postage,
                      Location       = record.Location,
                      Rate           = (double)record.CurrencyRate,
                      DateCreated    = record.DateCreated,
                      DateUpdated    = record.DateUpdated
                  }
                 )
        );

        private static readonly Func<DataContext, string, IQueryable<PurchaseRecordInfo>>
          LinqGetRecordsByUserId = CompiledQuery.Compile((DataContext context, string userId)
              => (
                  from record  in context.GetTable<PurchaseRecord>().Where(r => r.OperatorId.Equals(userId)).OrderByDescending(p => p.DateCreated)
                  join product in context.GetTable<Product>().OrderByDescending(p => p.DateCreated)
                  on record.ProductId equals product.ProductId
                  select new PurchaseRecordInfo {
                      Id             = record.Id,
                      OperatorId     = record.OperatorId,
                      PostOperatorId = record.PostOperatorId,
                      ProductName    = product.CName,
                      Name           = product.Name,
                      Cost           = record.Cost,
                      CostCurrency   = record.CostCurrency,
                      Count          = record.Count,
                      Postage        = record.Postage,
                      Location       = record.Location,
                      Rate           = (double) record.CurrencyRate,
                      DateCreated    = record.DateCreated,
                      DateUpdated    = record.DateUpdated
                  }
                 )
        );

        public override string TableName {
            get {
                return "D_PRODUCT_PURCHASE_RECORD";
            }
        }

        public ProductStockRepository ProductStock { get; set; }

        public PurchaseRecord GetRecord(string recordId) {
            return Execute(context => LinqGetRecord(context, recordId));
        }

        public List<PurchaseRecordInfo> ListAllRecords() {
            return Execute(context => LinqGetAllRecords(context).ToList());
        }

        public List<PurchaseRecordInfo> ListRecordsByUserId(string userId) {
            return Execute(context => LinqGetRecordsByUserId(context, userId).ToList());
        }

        public List<PurchaseRecordInfo> ListRecordsByProductId(string productId) {
            return Execute(context => LinqGetRecordsByProductId(context, productId).ToList());
        }

        public List<PurchaseRecordInfo> ListRecordsByKeyword(string keyword) {
            return Execute(context => LinqGetRecordsByKeyword(context, keyword).ToList());
        }

        public List<PurchaseRecordInfo> ListRecordsByLocation(int location) {
            return Execute(context => LinqGetRecordsByLocation(context, location).ToList());
        }

        public List<PurchaseRecord> ListRecordsByProductIdLocation(string productId, int location) {
            return Execute(context => LinqGetRecordsByProductIdLocation(context, productId, location).ToList());
        }

        public string CreateRecord(PurchaseRecord record) {
            return Execute((context, ts) => {
                InsertOrUpdateStock(context, record);
                context.GetTable<PurchaseRecord>().InsertOnSubmit(record);
                context.SubmitChanges();
                ts.Complete();
                return record.Id;
            });
        }

        private static void InsertOrUpdateStock(DataContext context, PurchaseRecord record) {
            var stock = LinqGetStockByLocationProductId(context, record.Location, record.ProductId);
            if (stock != null) {
                var count           = stock.Count    + record.Count;
                var currentprice    = stock.Price    * stock.Count;
                var currentpostage  = stock.Postage  * stock.Count;
                var incomingPrice   = record.Cost    * record.Count;
                var incomingPostage = record.Postage * record.Count;
                stock.DateUpdated   = DateTime.UtcNow;
                stock.Count         = count;
                stock.Price         = (currentprice + incomingPrice) / count;
                stock.Postage       = (currentpostage + incomingPostage) / count;
            } 
            else {
                context.GetTable<Stock>().InsertOnSubmit(new Stock {
                    Guid      = GuidUtil.GuidN(),
                    Location  = record.Location,
                    Postage   = record.Postage,
                    Price     = record.Cost,
                    Count     = record.Count,
                    ProductId = record.ProductId
                });
            }
        }

        public string TransferRecord(TransferRecordReq request) {
            return Execute((context, ts) => {
                var record = LinqGetRecord(context, request.RecordId);
                if (request.Count >= record.Count) {
                    record.Location       = request.TargetLocation;
                    record.Postage        = (decimal)request.Postage;
                    record.PostOperatorId = request.OperatorId;
                    record.DateUpdated    = DateTime.UtcNow;
                    context.SubmitChanges();
                    ts.Complete();
                    return record.Id;
                }

                record.Count       = record.Count - request.Count;
                record.DateUpdated = DateTime.UtcNow;
                var guid = GuidUtil.GuidN();
                context.GetTable<PurchaseRecord>().InsertOnSubmit(new PurchaseRecord {
                    Id              = guid,
                    ProductId       = record.ProductId,
                    Cost            = record.Cost,
                    CostCurrency    = record.CostCurrency,
                    Location        = request.TargetLocation,
                    Postage         = (decimal)request.Postage,
                    PostageCurrency = record.PostageCurrency,
                    CurrencyRate    = record.CurrencyRate,
                    Count           = request.Count,
                    OperatorId      = record.OperatorId,
                    PostOperatorId  = request.OperatorId,
                });

                context.SubmitChanges();
                ts.Complete();
                return guid;
            });
        }

        public void RemoveRecord(string recordId) {
            Execute((context, ts) => {
                var record = LinqGetRecord(context, recordId);
                context.GetTable<PurchaseRecord>().DeleteOnSubmit(record);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public PurchaseRecord UpdateRecord(string recordId, PurchaseRecordUpdator recordUpdator) {
            return Execute((context, ts) => {
                var record = LinqGetRecord(context, recordId);
                if (record == null) {
                    ts.Complete();
                    return null;
                }

                recordUpdator.Update(record);
                context.SubmitChanges();
                ts.Complete();
                return record;
            });
        }
        
        public PurchaseRecordSummary GetRecordSummary(string productId, int location) {
            var records = ListRecordsByProductIdLocation(productId, location);
            if (CollectionUtil.IsEmpty(records)) {
                return new PurchaseRecordSummary {
                    Location            = location,
                    ProductId           = productId,
                    TotalPostage        = 0,
                    TotalCost           = 0,
                    TotalNumOfPurchases = 0
                };
            }

            var totalNumOfRecord = records.Sum(r => r.Count);
            var totalPostage     = records.Where(r => r.Postage != 0).Sum(r => r.Postage * r.Count);
            var totalCost        = records.Where(r => r.Cost != 0).Sum(r => r.Cost * r.Count);

            return new PurchaseRecordSummary {
                   Location            = location,
                   ProductId           = productId,
                   TotalPostage        = (double) totalPostage,
                   TotalCost           = (double) totalCost,
                   TotalNumOfPurchases = totalNumOfRecord
            };

        }
    }
}